/****************************************************************************

 plugin.cpp - Imitates an LV2 plugin. Stores RDF data only.

Copyright (C) Michael R Fisher <mfisher31@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA

****************************************************************************/

/* Much of this file is based upon code found in elven which is
 * (c) Lars Luthman <lars.luthman@gmail.com>. Noted where applicable
 */

#include <string>
#include <map>

#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <turtleparser.hpp>
#include <namespaces.hpp>
#include <query.hpp>

#include "plugin.hpp"

using namespace PAQ;
using namespace std;

bool Plugin::open_bundle(const char* bundle) {
  if(m_valid) {
    return true;
  }

  m_bundle = bundle;
  int s = m_bundle.size()-1;
  if(m_bundle.substr(s,s) != "/") {
    m_bundle.append("/");
  }

  if(!parse_data_files()) {
    #ifndef NDEBUG
      cout << "RDF Data Invalid\n";
    #endif
    return false;
  }
string uri = "<"+m_uri+">";
  return scan_ports(uri);
}

bool Plugin::find_controls() {
 // if(!m_valid) return false;

  string uri = "<"+m_uri+">";

  Namespace lv2("<http://lv2plug.in/ns/lv2core#>");
  Namespace doap("<http://usefulinc.com/ns/doap#>");

  Variable symbol, index, port;

  vector<QueryResult> qr = select(index, symbol, port)
    .where(uri, lv2("port"), port)
    .where(port, lv2("index"), index)
    .where(port, lv2("symbol"), symbol)
    .where(port, rdf("type"), lv2("ControlPort"))
    .where(port, rdf("type"), lv2("InputPort"))
    .run(m_data);

  if (qr.size() == 0) {

    #ifndef NDEBUG
      cout << "Found no Control Ports";
    #endif

    return false;
  }

  for(int x = 0; x < qr.size(); x++) {
    signal_found_control(m_ports[x]);
  }

  return true;
}

// An unused function.
void Plugin::get_data_files() {
  string tmp = "";
  DIR *dp;
  struct dirent *ep;

  dp = opendir(m_bundle.c_str());
  if (dp != NULL)
  {
    while (ep = readdir (dp))
    {
      tmp = ep->d_name;
      if(tmp.find(".ttl") != tmp.npos)
      {
        m_data_files.push_back(m_bundle + tmp);
      }
    } //while loop

    (void) closedir (dp);
  }
}

bool Plugin::parse_data_files() {
  vector<QueryResult> qr;
  string uriref = string("<") + m_uri + ">";
  TurtleParser tp;
  string manifest = m_bundle+"manifest.ttl";
  if (!tp.parse_ttl_file(manifest,m_data)) {
    #ifndef NDEBUG
      cout << manifest << " is invalid\n";
    #endif

    return false;
  }

  Namespace lv2("<http://lv2plug.in/ns/lv2core#>");
  Namespace ev("<http://lv2plug.in/ns/ext/event#>");

  Variable see_also;
  qr = select(see_also)
    .where(uriref, rdf("type"), lv2("Plugin"))
    .where(uriref, rdfs("seeAlso"), see_also)
    .run(m_data);

  for (uint32_t i = 0; i < qr.size(); ++i) {
    const string& str = qr[i][see_also]->name;

    if (str.size() >= 10 && str.substr(0, 9) == "<file:///") {

      m_data_files.push_back(str.substr(8, str.size() - 9));

      if(!tp.parse_ttl_url(str,m_data)) {

        #ifndef NDEBUG
          cout << "Error parsing additional files\n";
        #endif

        return false;
      }
    }
  }

  fill_plugin_info();

  return true;
}

bool Plugin::scan_ports(std::string& parent) {

  Namespace lv2("<http://lv2plug.in/ns/lv2core#>");
  Namespace doap("<http://usefulinc.com/ns/doap#>");

  Variable name, symbol, index, port;

  vector<QueryResult>qr = select(index, symbol, port, name)
    .where(parent, lv2("port"), port)
    .where(port, lv2("index"), index)
    .where(port, lv2("symbol"), symbol)
    .where(port, lv2("name"), name)
    .run(m_data);

  if (qr.size() == 0) {

    #ifndef NDEBUG
      cout << "Did not find any ports.\n";
    #endif

    return false;
  }

  for (unsigned j = 0; j < qr.size(); ++j) {

    Port port;
    port.name = qr[j][name]->name;
    port.index = atoi(qr[j][index]->name.c_str());
    port.symbol = qr[j][symbol]->name;
    port.default_value = 0.0;
    port.minimum_value = 0.0;
    port.maximum_value = 0.0;

    m_symbol_map[port.symbol] = port.index;

    m_ports.push_back(port);

    #ifndef NDEBUG
      cout << "Adding Port to Symbol Map [" << port.index << "][" << port.symbol << "]" << endl;
    #endif

  } //for

  // default values ( From Elven )
  Variable default_value;
  qr = select(index, default_value)
    .where(parent, lv2("port"), port)
    .where(port, lv2("index"), index)
    .where(port, lv2("default"), default_value)
    .run(m_data);
  for (unsigned j = 0; j < qr.size(); ++j) {
    unsigned p = atoi(qr[j][index]->name.c_str());
    if (p >= m_ports.size())
      return false;
    m_ports[p].default_value =
      atof(qr[j][default_value]->name.c_str());
  }

  // minimum values ( From Elven )
  Variable min_value;
  qr = select(index, min_value)
    .where(parent, lv2("port"), port)
    .where(port, lv2("index"), index)
    .where(port, lv2("minimum"), min_value)
    .run(m_data);
  for (unsigned j = 0; j < qr.size(); ++j) {
    unsigned p = atoi(qr[j][index]->name.c_str());
    if (p >= m_ports.size())
      return false;
    m_ports[p].minimum_value =
      atof(qr[j][min_value]->name.c_str());
  }

  // maximum values ( From Elven )
  Variable max_value;
  qr = select(index, max_value)
    .where(parent, lv2("port"), port)
    .where(port, lv2("index"), index)
    .where(port, lv2("maximum"), max_value)
    .run(m_data);
  for (unsigned j = 0; j < qr.size(); ++j) {
    unsigned p = atoi(qr[j][index]->name.c_str());
    if (p >= m_ports.size())
      return false;
    m_ports[p].maximum_value =
      atof(qr[j][max_value]->name.c_str());

  }

// #ifndef NDEBUG
//    print_port_data();
//  #endif

  return true;
}

string Plugin::get_name() {

  string uri = string("<")+m_uri+">";

  Namespace lv2("<http://lv2plug.in/ns/lv2core#>");
  Namespace doap("<http://usefulinc.com/ns/doap#>");

  Variable name;

  vector<QueryResult> qr = select(name)
    .where(uri, rdf("type"), lv2("Plugin"))
    .where(uri, doap("name"), name)
    .run(m_data);

  return qr[0][name]->name;

}

bool Plugin::fill_plugin_info() {
  string uri = string("<")+m_uri+">";

  Namespace lv2("<http://lv2plug.in/ns/lv2core#>");
  Namespace doap("<http://usefulinc.com/ns/doap#>");
  Namespace foaf("<http://xmlns.com/foaf/0.1/>");

  Variable name,author,homepage,email,license,docs,maintainer;

  vector<QueryResult> qr = select(name)
    .where(uri, rdf("type"), lv2("Plugin"))
    .where(uri, doap("name"), name)
    .run(m_data);

    if(qr.size() > 0) {
      m_info.name = qr[0][name]->name;
    }

    qr = select(docs)
        .where(uri, rdf("type"), lv2("Plugin"))
        .where(uri, lv2("documentation"), docs)
        .run(m_data);

    if(qr.size() > 0) {
      m_info.docs = qr[0][docs]->name;
    }

    qr = select(license)
        .where(uri, rdf("type"), lv2("Plugin"))
        .where(uri, doap("license"), license)
        .run(m_data);

    if(qr.size() > 0) {
      m_info.license = qr[0][license]->name;
    }

    qr = select(author,email,homepage,maintainer)
        .where(uri, doap("maintainer"),maintainer)
        .where(maintainer, foaf("name"), author)
        .where(maintainer, foaf("mbox"), email)
        .where(maintainer, foaf("homepage"), homepage)
        .run(m_data);

    if(qr.size() > 0) {
      m_info.author = qr[0][author]->name;
      m_info.email = qr[0][email]->name;
      m_info.homepage = qr[0][homepage]->name;
    }

 //   cout << m_info.name;
}

// This is not a very nice looking function. I use it for debugging only.
void Plugin::print_port_data() {
  cout << m_info.name << endl
       << m_info.author << endl
  << endl;

  for(int x = 0; x < m_ports.size(); x++) {
    cout << m_ports[x].index << endl
        << m_ports[x].symbol << endl
        << m_ports[x].minimum_value << endl
        << m_ports[x].maximum_value << endl
        << m_ports[x].default_value << endl
    << endl;
  }
}
